/**
 * Copyright (c) 2011-2017, James Zhan 詹波 (jfinal@126.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.jfinal.core;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.jfinal.aop.Interceptor;
import com.jfinal.aop.InterceptorManager;
import com.jfinal.config.Interceptors;
import com.jfinal.config.Routes;
import com.jfinal.config.Routes.Route;

/**
 * ActionMapping
 */
final class ActionMapping {
	
	private static final String SLASH = "/";
	private Routes routes;
	// private Interceptors interceptors;
	
	private final Map<String, Action> mapping = new HashMap<String, Action>();
	
	ActionMapping(Routes routes, Interceptors interceptors) {
		this.routes = routes;
		// this.interceptors = interceptors;
	}
	
	private Set<String> buildExcludedMethodName() {
		
		Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Controller.class.getMethods();
		
		for ( Method m : methods ) {
			
			if ( m.getParameterTypes().length == 0 ) {
				
				excludedMethodName.add( m.getName() );
				
			}
		}
		
		return excludedMethodName;
	}
	
	private List<Routes> getRoutesList() {
		
		List<Routes> routesList = Routes.getRoutesList();
		List<Routes> ret = new ArrayList<Routes>( routesList.size() + 1 );
		
		ret.add( routes );
		ret.addAll( routesList );
		
		return ret;
	}
	
	void buildActionMapping() {
		
		mapping.clear();
		
		Set<String> excludedMethodName = buildExcludedMethodName();
		InterceptorManager interMan = InterceptorManager.me();
		
		for ( Routes routes : getRoutesList() ) {
			
			for ( Route route : routes.getRouteItemList() ) {
				
				Class<? extends Controller> controllerClass = route.getControllerClass();
				Interceptor[] controllerInters = interMan.createControllerInterceptor( controllerClass );
				
				boolean sonOfController = ( controllerClass.getSuperclass() == Controller.class );
				Method[] methods = ( sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods() );
				
				for ( Method method : methods ) {
					
					String methodName = method.getName();
					if ( excludedMethodName.contains( methodName ) || method.getParameterTypes().length != 0 ) {
						continue ;
					}
					
					if ( sonOfController && !Modifier.isPublic( method.getModifiers() ) ) {
						continue ;
					}
					
					Interceptor[] actionInters = interMan.buildControllerActionInterceptor( routes.getInterceptors(), controllerInters, controllerClass, method );
					String controllerKey = route.getControllerKey();
					
					ActionKey ak = method.getAnnotation( ActionKey.class );
					String actionKey;
					if ( ak != null ) {
						
						actionKey = ak.value().trim();
						if ( "".equals( actionKey ) ) {
							throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");
						}
							
						if ( !actionKey.startsWith( SLASH ) ) {
							actionKey = SLASH + actionKey;
						}
						
					}
					else if ( methodName.equals( "index" ) ) {
						actionKey = controllerKey;
					}
					else {
						actionKey = controllerKey.equals( SLASH ) ? SLASH + methodName : controllerKey + SLASH + methodName;
					}
					
					Action action = new Action( controllerKey, actionKey, controllerClass, method, methodName, actionInters, route.getFinalViewPath( routes.getBaseViewPath() ) );
					if ( mapping.put( actionKey, action ) != null ) {
						throw new RuntimeException( buildMsg( actionKey, controllerClass, method ) );
					}
				}
			}
			
		}
		
		routes.clear();
		
		// support url = controllerKey + urlParas with "/" of controllerKey
		Action action = mapping.get( "/" );
		if ( action != null ) {
			mapping.put( "", action );
		}
		
	}
	
	private static final String buildMsg(String actionKey, Class<? extends Controller> controllerClass, Method method) {
		
		StringBuilder sb = new StringBuilder( "The action \"" )
			.append( controllerClass.getName() ).append( "." )
			.append( method.getName() ).append( "()\" can not be mapped, " )
			.append( "actionKey \"" ).append(actionKey).append( "\" is already in use." );
		
		String msg = sb.toString();
		System.err.println( "\nException: " + msg );
		
		return msg;
	}
	
	/**
	 * Support four types of url
	 * 1: http://abc.com/controllerKey                 ---> 00
	 * 2: http://abc.com/controllerKey/para            ---> 01
	 * 3: http://abc.com/controllerKey/method          ---> 10
	 * 4: http://abc.com/controllerKey/method/para     ---> 11
	 * The controllerKey can also contains "/"
	 * Example: http://abc.com/uvw/xyz/method/para
	 */
	Action getAction(String url, String[] urlPara) {
		
		Action action = mapping.get( url );
		if ( action != null ) {
			return action;
		}
		
		// --------
		int i = url.lastIndexOf( '/' );
		if ( i != -1 ) {
			
			action = mapping.get( url.substring( 0, i ) );
			urlPara[0] = url.substring( i + 1 );
			
		}
		
		return action;
	}
	
	List<String> getAllActionKeys() {
		
		List<String> allActionKeys = new ArrayList<String>( mapping.keySet() );
		Collections.sort( allActionKeys );
		
		return allActionKeys;
	}
}







